package com.imxinxin.common.tools.scan;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 覆盖率提升类 会调用get set等方法
 *
 * @author xieyuxin
 * @date 2022-07
 */
public class CoverageScanTest {

    /**
     * d
     */
    private CoverageScanTest() {

    }

    /**
     * 1
     */
    private static final Logger logger = Logger.getLogger(CoverageScanTest.class.getName());
    /**
     * 1
     */
    private static final Map<Class<?>, Object> BASE_VAL;
    /**
     * 1
     */
    private static final List<String> METHOD_PREFIX = new ArrayList<>();

    static {
        BASE_VAL = initBaseObj();
        METHOD_PREFIX.add("get");
        METHOD_PREFIX.add("set");
        METHOD_PREFIX.add("is");
        METHOD_PREFIX.add("toString");
        METHOD_PREFIX.add("hashCode");
        METHOD_PREFIX.add("equals");

        METHOD_PREFIX.add("compare");
        METHOD_PREFIX.add("gen");

    }


    /**
     * t
     *
     * @param packageName t
     */
    public static void coverageTest(String... packageName) {
        if (packageName == null) {
            return;
        }
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        for (String pn : packageName) {
            Set<String> set;
            try {
                set = PackageScanTool.scan(pn);
            } catch (Exception e) {
                continue;
            }
            if (set.isEmpty()) {
                continue;
            }
            for (String s : set) {
                try {
                    if (s != null && !s.trim().equals("")) {
                        invokeSetGetToString(classLoader.loadClass(s));
                    }
                } catch (ClassNotFoundException e) {
                    logger.info(e.getMessage());
                }
            }
        }
    }


    /**
     * t
     *
     * @return t
     */
    public static Map<Class<?>, Object> initBaseObj() {
        Map<Class<?>, Object> map = new HashMap<>(16);
        map.put(String.class, "String");
        map.put(Byte.class, (byte) 2);
        map.put(Short.class, (short) 2);
        map.put(Integer.class, 2);
        map.put(Long.class, 2L);
        map.put(Boolean.class, true);
        map.put(Date.class, new Date());
        map.put(Character.class, 'C');
        map.put(Double.class, 2.0);
        map.put(Float.class, (float) 2.0);
        map.put(long.class, 1L);
        map.put(int.class, 1);
        map.put(short.class, (short) 1);
        map.put(byte.class, (byte) 1);
        map.put(char.class, 'c');
        map.put(double.class, 1.0);
        map.put(float.class, (float) 1.0);
        map.put(boolean.class, true);
        map.put(List.class, new ArrayList<>(0));
        map.put(Map.class, new HashMap<>(0));
        map.put(Set.class, new HashSet<>(0));
        map.put(BigDecimal.class, new BigDecimal(1));
        map.put(LocalDate.class, LocalDate.now());
        map.put(LocalDateTime.class, LocalDateTime.now());
        map.put(Object.class, "Object");

        Map<Class<?>, Object> map1 = new HashMap<>(16);
        for (Class<?> clz : map.keySet()) {
            Object array = Array.newInstance(clz, 1);
            Class<?> arrayClz = array.getClass();
            map1.put(arrayClz, array);
        }
        map.putAll(map1);
        return map;
    }


    /**
     * t
     *
     * @param clz t
     */
    public static void invokeSetGetToString(Class<?> clz) {
        Object obj = getVal(clz);
        if (obj == null) {
            return;
        }
        Method[] methods = clz.getMethods();
        if (methods.length == 0) {
            return;
        }
        for (Method method : methods) {
            if (method.getDeclaringClass() != clz) {
                continue;
            }
            invokeGetSet(method, obj);
        }

    }

    /**
     * t
     *
     * @param method t
     * @param obj    t
     */
    public static void invokeGetSet(Method method, Object obj) {
        String name = method.getName();
        boolean match = false;
        for (String s : METHOD_PREFIX) {
            if (name.startsWith(s)) {
                match = true;
                break;
            }
        }
        if (!match) {
            return;
        }

        Class<?>[] paramCls = method.getParameterTypes();
        try {
            if (paramCls.length == 0) {
                method.invoke(obj);
                return;
            }
            Object[] param = new Object[paramCls.length];
            for (int i = 0; i < param.length; i++) {
                param[i] = getVal(paramCls[i]);
            }
            if (paramCls.length == 1) {
                method.invoke(obj, param[0]);
            }
            if (paramCls.length == 2) {
                method.invoke(obj, param[0], param[1]);
            }
            if (paramCls.length == 3) {
                method.invoke(obj, param[0], param[1], param[2]);
            }
        } catch (Exception e) {
            if (logger.isLoggable(Level.FINE)) {
                logger.info(e.getMessage());
            }
        }

    }


    /**
     * t
     *
     * @param clz  t
     * @param <T>t
     * @return t
     */
    public static <T> T getVal(Class<T> clz) {

        T t = getBaseVal(clz);
        if (t != null) {
            return t;
        }
        if (clz.isInterface() || clz.isAnnotation()) {
            return null;
        }


        try {
            if (clz.isArray()) {
                return getArray(clz);
            }
            if (clz.isEnum()) {
                return getEnum(clz);
            }
            t = getValByConstructor(clz);
        } catch (Exception e) {
            if (logger.isLoggable(Level.FINE)) {
                logger.info(e.getMessage());
            }
        }
        return t;
    }


    /**
     * t
     *
     * @param clz t
     * @param <T> t
     * @return t
     * @throws InstantiationException t
     * @throws IllegalAccessException t
     */
    public static <T> T getValByConstructor(Class<T> clz) throws InstantiationException, IllegalAccessException {
        return clz.newInstance();
    }


    /**
     * t
     *
     * @param clz t
     * @param <T> t
     * @return t
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBaseVal(Class<T> clz) {
        return (T) BASE_VAL.get(clz);
    }

    /**
     * t
     *
     * @param clz t
     * @param <T> t
     * @return t
     * @throws ClassNotFoundException t
     */
    public static <T> T getArray(Class<T> clz) throws ClassNotFoundException {
        String canonicalName = clz.getCanonicalName();
        canonicalName = canonicalName.substring(0, canonicalName.length() - 2);
        Class<?> c = Class.forName(canonicalName);
        return (T) Array.newInstance(c, 1);
    }

    /**
     * t
     *
     * @param clz t
     * @param <T> t
     * @return t
     * @throws IllegalAccessException    t
     * @throws NoSuchMethodException     t
     * @throws InvocationTargetException t
     */
    @SuppressWarnings("unchecked")
    public static <T> T getEnum(Class<T> clz) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        Method method = clz.getMethod("values");
        Object obj = method.invoke(null);
        if (obj != null && Array.getLength(obj) != 0) {
            return (T) Array.get(obj, 0);
        }
        return null;
    }

}